package il.ac.bgu.cs.heuristics.core;

import il.ac.bgu.cs.heuristics.graph.State;
import il.ac.bgu.cs.heuristics.graph.types.IActionGraph;
import il.ac.bgu.cs.heuristics.graph.types.IPath;
import il.ac.bgu.cs.heuristics.graph.types.IState;
import il.ac.bgu.cs.heuristics.graph.types.ISubstitution;
import il.ac.bgu.cs.heuristics.utils.CollectionUtils;

import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;

public class heuristicsEngineLightAOSearch extends heuristicsEngine implements
		IHeuristicsEngine {

	private IState	init		= null;
	int				andNode		= 0;
	int				restNode	= 0;

	public heuristicsEngineLightAOSearch(IActionGraph aGraph) {
		super(aGraph);
		// TODO Auto-generated constructor stub
	}

	@Override
	public int getHValue(IState init, IState goal) {
		this.init = init;
		boolean u = aGraph.setInitState(this.init);
		IState cleanGoal = State.createState(init
				.getNotContainedSubstitution(goal.getSubstitutions()));
		AONode rootNode = new AONode(0, 0, cleanGoal, null, null);
		SearchTree(rootNode);
		log("Rest " + (restNode - andNode));
		log("And " + andNode);
		log("Done +++++++=");
		return rootNode.getFunctionF();
	}

	private void SearchTree(AONode rootNode) {
		Queue<AONode> Squeue = new PriorityQueue<AONode>(11,
				new CompareAONodeForSqueue<AONode>());
		Squeue.add(rootNode);
		while (!rootNode.isSolve()) {
			// 2.1
			AONode currentNode = chooseCurrentNodeFromPSG(rootNode);
			log(" " + currentNode.getLevel());
			if (currentNode == null)
				throw new IllegalArgumentException("current Node is null");

			currentNode.expend(aGraph);

			// 2.2
			Squeue.clear();
			Squeue.add(currentNode);
			iterateSqueue(Squeue);
		}

	}

	private void iterateSqueue(Queue<AONode> Squeue) {
		int e = Integer.MAX_VALUE;
		while (!Squeue.isEmpty()) {
			AONode node = Squeue.poll();
			if (node.getSuccessors().size() == 0)
				e = Integer.MAX_VALUE;
			if (node.IsTypeOf(NodeType.OR)) {
				e = handleOrNodes(node);
			}
			if (node.IsTypeOf(NodeType.AND)) {
				e = handleAndNodes(node);
			}
			if (node.SetFunctionF(e) || node.isSolve()) {
				insertPredecessorsToQueue(node, Squeue);
			}
		}
	}

	private static void insertPredecessorsToQueue(AONode node,
			Queue<AONode> squeue) {
		if (node.predecessor != null) {
			squeue.add(node.predecessor);
			insertPredecessorsToQueue(node.predecessor, squeue);
		}
	}

	int handleAndNodes(AONode node) {
		int e = 0;
		// List<AONode> SolvedList = CollectionUtils.newArrayList();
		node.SetMarkedSuccessors(node.successors);
		Set<ISubstitution> reachedSubs = CollectionUtils.newLinkedHashSet();
		int solvedSum = 0;
		int solvedNodeNumber = 0;// if all child are solved and we didnt have
		// all proposition in reached subs then be
		// need to solve the node
		for (AONode childNode : node.getSuccessors()) {
			e += (childNode.getFunctionF() + childNode.getPathLength());
			if (childNode.isSolve()
					&& !reachedSubs.containsAll(childNode.getFirstEffect()
							.getSubstitutions())) {
				reachedSubs.addAll(childNode.getFirstEffect()
						.getSubstitutions());
				solvedSum += (childNode.getFunctionF() + childNode
						.getPathLength());
				// solvedSum = Math.max(solvedSum,
				// (childNode.getFunctionF() + childNode.getPathLength()));
				solvedNodeNumber++;
			}
		}

		// if true this And node is done
		if (reachedSubs.containsAll(node.getGoal().getSubstitutions())
				|| solvedNodeNumber == node.getSuccessors().size()) {
			e = solvedSum;
			node.SetAsSolved();
		}
		return e;
	}

	int handleOrNodes(AONode node) {
		int e = Integer.MAX_VALUE;
		AONode minNode = null;
		for (AONode childNode : node.getSuccessors()) {
			// give preference to solved childs
			if (childNode.getFunctionF() + childNode.getPathLength() == e) {
				if (minNode.isSolve())
					continue;
				else {
					e = childNode.getFunctionF() + childNode.getPathLength();
					minNode = childNode;
				}
			}

			if (childNode.getFunctionF() + childNode.getPathLength() < e) {
				e = childNode.getFunctionF() + childNode.getPathLength();
				minNode = childNode;
			}
		}

		node.SetMarkedSuccessors(minNode);
		if (minNode.isSolve())
			node.SetAsSolved();
		return e;
	}

	private AONode chooseCurrentNodeFromPSG(AONode node) {
		Queue<AONode> BFSqueue = new LinkedList();
		BFSqueue.add(node);
		while (!BFSqueue.isEmpty()) {
			AONode currentNode = BFSqueue.poll();
			if (currentNode.isSolve()
					|| currentNode.IsTypeOf(NodeType.DEAD_END))
				continue;
			if (currentNode.IsTypeOf(NodeType.TIP_NODE))
				return currentNode;
			else {
				BFSqueue.addAll(currentNode.getMarkedSuccessors());
			}
		}
		return null;
	}

	private class AONode {
		NodeType		type				= NodeType.TIP_NODE;
		boolean			solved				= false;
		int				pathLength			= -1;
		int				functionF			= Integer.MAX_VALUE;
		int				level				= -1;
		IState			goal				= null;
		IState			firstEffects		= null;

		AONode			predecessor			= null;
		List<AONode>	markedSuccessors	= null;
		List<AONode>	successors			= null;

		void SetMarkedSuccessors(List<AONode> successors) {
			this.markedSuccessors = successors;
		}

		void SetMarkedSuccessors(AONode successor) {
			if (this.markedSuccessors == null)
				this.markedSuccessors = CollectionUtils.newArrayList();
			this.markedSuccessors.clear();
			this.markedSuccessors.add(successor);
		}

		IState getGoal() {
			return this.goal;
		}

		IState getFirstEffect() {
			return this.firstEffects;
		}

		int getFunctionF() {
			return functionF;
		}

		List<AONode> getMarkedSuccessors() {
			if (IsTypeOf(NodeType.TIP_NODE) || successors == null)
				throw new IllegalArgumentException(
						"getMarkedSuccessors was called before i could expend the node");
			if (IsTypeOf(NodeType.OR))
				return markedSuccessors;
			if (IsTypeOf(NodeType.AND))
				return markedSuccessors;
			return null;
		}

		NodeType GetNodeType(Set<IState> setOfEffects) {
			restNode++;
			if (this.IsTypeOf(NodeType.TERMINAL))
				return NodeType.TERMINAL;
			if (setOfEffects.size() == 0)
				return NodeType.DEAD_END;
			for (IState effects : setOfEffects) {
				if (effects.getSubstitutions().containsAll(
						this.goal.getSubstitutions()))
					return NodeType.OR;
			}
			andNode++;
			return NodeType.AND;
		}

		int getNodeHValue() {
			return this.goal.getSubstitutions().size();
		}

		boolean IsTypeOf(NodeType type) {
			return this.type == type;
		}

		int getLevel() {
			return this.level;
		}

		int getPathLength() {
			return this.pathLength;
		}

		boolean isSolve() {
			return this.solved;
		}

		void SetAsSolved() {
			this.solved = true;
		}

		List<AONode> getSuccessors() {
			return this.successors;
		}

		public AONode(int pathLength, int level, IState goal,
				IState firstEffects, AONode predecessor) {
			this.pathLength = pathLength;
			this.level = level;
			this.goal = goal;
			this.firstEffects = firstEffects;
			this.predecessor = predecessor;
			if (this.goal.getSubstitutions().size() == 0) {
				this.type = NodeType.TERMINAL;
				this.solved = true;
			}
			SetFunctionF(this.goal.getSubstitutions().size());
		}

		boolean SetFunctionF(int functionF) {
			if (this.functionF == functionF)
				return false;
			else {
				this.functionF = functionF;
				return true;
			}
		}

		boolean expend(IActionGraph aGraph) {
			if (IsTypeOf(NodeType.TERMINAL))
				return false;
			List<AONode> sonsSet = CollectionUtils.newArrayList();
			Set<IState> subgoals = aGraph.getNonContradictingEffects(this.goal);
			subgoals = filterGoals(init, this.goal, subgoals,
					new NotUsefulGoalsStateFilter());
			this.type = GetNodeType(subgoals);
			for (IState child : subgoals) {
				sonsSet.add(createChild(aGraph, child));
			}
			this.successors = sonsSet;
			return true;

		}

		private AONode createChild(IActionGraph aGraph, IState child) {
			IPath path = aGraph.getShortestPath(init, child);
			Set<ISubstitution> ConstraintsList = path.getConstraints();
			ConstraintsList = init.getNotContainedSubstitution(ConstraintsList);
			return new AONode(path.weight(), this.level + 1, State
					.createState(ConstraintsList), child, this);
		}

	}

	protected enum NodeType {
		AND, OR, TERMINAL, TIP_NODE, DEAD_END
	};

	class CompareAONodeForSqueue<T extends AONode> implements Comparator<T> {

		@Override
		public int compare(T arg0, T arg1) {
			return arg1.getLevel() - arg0.getLevel();
		}
	}

}
